#include "arminst.h"

#include <cstddef>
#include <ios>
#include <string>
#include <vector>

#include "arithmetic.h"
#include "armblock.h"
#include "armfunction.h"
#include "types.h"
#include "utils.h"

using namespace Utils;

namespace Arm {
    std::string GenCond(ArithOp cond) {
        switch (cond) {
            case ArithOp::Equal:
                return "eq";
            case ArithOp::NotEqual:
                return "ne";
            case ArithOp::GreaterEqual:
                return "ge";
            case ArithOp::Greater:
                return "gt";
            case ArithOp::LessEqual:
                return "le";
            case ArithOp::Less:
                return "lt";
            default:
                return "";
        }
    }

    BinaryOp::BinaryOp(Register rd, Register rs, Register rt, ArithOp op)
        : rd(rd), rs(rs), rt(rt), op(op) {
        Assert(rd.type == rs.type && rd.type == rt.type, "biop type error");
        Assert(op == ArithOp::Add || op == ArithOp::Sub || op == ArithOp::Mul ||
                   op == ArithOp::Div,
               "biop op error");
    }
    void BinaryOp::GenAsm(std::ostream& os) const {
        os << "    ";
        bool intInst = rd.type == Type::Basic::Int;
        if (op == ArithOp::Add)
            os << (intInst ? "add" : "vadd.f32");
        else if (op == ArithOp::Sub)
            os << (intInst ? "sub" : "vsub.f32");
        else if (op == ArithOp::Mul)
            os << (intInst ? "mul" : "vmul.f32");
        else if (op == ArithOp::Div)
            os << (intInst ? "sdiv" : "vdiv.f32");

        os << " ", rd.GenAsm(os);
        os << ", ", rs.GenAsm(os);
        os << ", ", rt.GenAsm(os);
        os << "\n";
    }

    Cmp::Cmp(Register rs, Register rt) : rs(rs), rt(rt) {
        Assert(rs.type == rt.type, "biop type error");
    }
    void Cmp::GenAsm(std::ostream& os) const {
        os << "    ";
        bool intInst = rs.type == Type::Basic::Int;
        os << (intInst ? "cmp" : "vcmp");
        os << " ", rs.GenAsm(os);
        os << ", ", rt.GenAsm(os);
        os << "\n";
    }

    BinaryOpImm::BinaryOpImm(Register rd, Register rs, int rt, ArithOp op)
        : rd(rd), rs(rs), rt(rt), op(op) {
        Assert(rd.type == rs.type && rd.type == Type::Basic::Int,
               "biop type error");
        Assert(op == ArithOp::Add || op == ArithOp::Sub, "biop op error");
        Assert(0 <= rt && rt < 4096, "biop imm error");
    }
    void BinaryOpImm::GenAsm(std::ostream& os) const {
        os << "    ";
        os << (op == ArithOp::Add ? "add" : "sub");
        os << " ", rd.GenAsm(os);
        os << ", ", rs.GenAsm(os);
        os << ", #" << rt << "\n";
    }

    UnaryOp::UnaryOp(Register rd, Register rs, ArithOp op)
        : rd(rd), rs(rs), op(op) {
        if (op == ArithOp::IntCast || op == ArithOp::FloatCast) {
            Assert(rd.type == rs.type && rs.type == Type::Basic::Float,
                   "uop type error");
        } else if (op == ArithOp::Add || op == ArithOp::Sub) {
            Assert(rd.type == rs.type, "uop type error");
        } else {
            Err("uop op error");
        }
    }
    void UnaryOp::GenAsm(std::ostream& os) const {
        os << "    ";
        bool intInst = rd.type == Type::Basic::Int;
        if (op == ArithOp::IntCast) {
            // vcvt.s32.f32 (F->I)
            os << "vcvt.s32.f32 ", rd.GenAsm(os);
            os << ", ", rs.GenAsm(os);
        } else if (op == ArithOp::FloatCast) {
            // vcvt.f32.s32 (I->F)
            os << "vcvt.f32.s32 ", rd.GenAsm(os);
            os << ", ", rs.GenAsm(os);
        } else if (op == ArithOp::Add) {
            os << (intInst ? "mov" : "vmov.f32");
            os << " ", rd.GenAsm(os);
            os << ", ", rs.GenAsm(os);
        } else if (op == ArithOp::Sub) {
            os << (intInst ? "rsb" : "vneg.f32");
            os << " ", rd.GenAsm(os);
            os << ", ", rs.GenAsm(os);
            if (intInst) os << ", #0";
        }
        os << "\n";
    }

    Mov::Mov(Register rd, Register rs) : rd(rd), rs(rs) {}
    void Mov::GenAsm(std::ostream& os) const {
        os << "    ";
        bool intDest = rd.type == Type::Basic::Int;
        bool intSrc = rs.type == Type::Basic::Int;
        if (intDest && intSrc)
            os << "mov";
        else if (!intDest && !intSrc)
            os << "vmov.f32";
        else
            os << "vmov";
        os << " ", rd.GenAsm(os);
        os << ", ", rs.GenAsm(os);
        os << "\n";
    }

    LoadAddr::LoadAddr(Register rd, const std::string& name)
        : rd(rd), name(name) {
        Assert(rd.type == Type::Basic::Int, "load type error");
    }
    void LoadAddr::GenAsm(std::ostream& os) const {
        os << "    movw ", rd.GenAsm(os);
        os << ", #:lower16:" << name;
        os << "\n    movt ", rd.GenAsm(os);
        os << ", #:upper16:" << name << "\n";
    }

    LoadImm::LoadImm(Register rd, int imm) : rd(rd), imm(imm) {
        Assert(rd.type == Type::Basic::Int, "load type error");
    }
    void LoadImm::GenAsm(std::ostream& os) const {
        os << "    ";
        if (0 <= imm && imm < 65536) {
            os << "mov ", rd.GenAsm(os);
            os << ", #" << imm;
        } else {
            os << "movw ", rd.GenAsm(os);
            os << ", #" << (imm & 65535);
            os << "\n    movt ", rd.GenAsm(os);
            os << ", #" << (imm >> 16 & 65535);
        }
        os << "\n";
    }

    LoadFloat::LoadFloat(Register rd, const std::string& lbl)
        : rd(rd), lbl(lbl) {
        Assert(rd.type == Type::Basic::Float, "load type error");
    }
    void LoadFloat::GenAsm(std::ostream& os) const {
        os << "    vldr.32 ", rd.GenAsm(os);
        os << ", " << lbl << "\n";
    }

    Load::Load(Register rd, MemObj* mem) : rd(rd), mem(mem) {}
    void Load::GenAsm(std::ostream& os) const {
        os << "    ";
        os << (rd.type == Type::Basic::Int ? "ldr" : "vldr");
        os << " ", rd.GenAsm(os);
        os << ", ", mem->GenAsm(os);
        os << "\n";
    }

    Store::Store(Register rs, MemObj* mem) : rs(rs), mem(mem) {}
    void Store::GenAsm(std::ostream& os) const {
        os << "    ";
        os << (rs.type == Type::Basic::Int ? "str" : "vstr");
        os << " ", rs.GenAsm(os);
        os << ", ", mem->GenAsm(os);
        os << "\n";
    }

    Call::Call(const std::string& name) : name(name) {}
    void Call::GenAsm(std::ostream& os) const {
        os << "    bl " << name << "\n";
    }

    void Terminator::GenJumpAsm(std::ostream& os) const {
        auto dest = GetDefaultJump();
        if (!dest) return;
        os << "    b " << dest->name << "\n";
    }

    Jump::Jump(ArmBlock* nxt) : nxt(nxt) {}
    void Jump::GenAsm(std::ostream& os) const {}
    std::vector<ArmBlock*> Jump::GetNext() const { return {nxt}; }
    ArmBlock* Jump::GetDefaultJump() const { return nxt; }

    JumpCond::JumpCond(ArithOp cond, ArmBlock* t, ArmBlock* f)
        : cond(cond), t(t), f(f) {}
    void JumpCond::GenAsm(std::ostream& os) const {
        os << "    b" << GenCond(cond) << " " << t->name << "\n";
    }
    std::vector<ArmBlock*> JumpCond::GetNext() const { return {t, f}; }
    ArmBlock* JumpCond::GetDefaultJump() const { return f; }

    void Ret::GenAsm(std::ostream& os) const {}
    std::vector<ArmBlock*> Ret::GetNext() const { return {}; }
    ArmBlock* Ret::GetDefaultJump() const { return nullptr; }

    Push::Push(Register reg) : regs({reg}), type(reg.type) {}
    Push::Push(const std::vector<Register>& regs) : regs(regs) {
        Assert(!regs.empty(), "push regs empty");
        type = regs[0].type;
        for (auto r : regs) Assert(r.type == type, "push type error");
    }
    void Push::GenAsm(std::ostream& os) const {
        os << "    ";
        os << (type == Type::Basic::Int ? "push" : "vpush") << " {";
        int n = (int)regs.size();
        for (int i = 0; i < n; ++i) {
            if (i) os << ", ";
            regs[i].GenAsm(os);
        }
        os << "}\n";
    }

    Pop::Pop(Register reg) : regs({reg}), type(reg.type) {}
    Pop::Pop(const std::vector<Register>& regs) : regs(regs) {
        Assert(!regs.empty(), "pop regs empty");
        type = regs[0].type;
        for (auto r : regs) Assert(r.type == type, "pop type error");
    }
    void Pop::GenAsm(std::ostream& os) const {
        os << "    ";
        os << (type == Type::Basic::Int ? "pop" : "vpop") << " {";
        int n = (int)regs.size();
        for (int i = 0; i < n; ++i) {
            if (i) os << ", ";
            regs[i].GenAsm(os);
        }
        os << "}\n";
    }

}  // namespace Arm